package com.zh.risk.service.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateField;
import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.ql.util.express.DefaultContext;
import com.ql.util.express.ExpressRunner;
import com.zh.common.aop.SpecialAop;
import com.zh.common.entity.JSONResult;
import com.zh.common.exception.MyServiceException;
import com.zh.risk.dao.BlackListDao;
import com.zh.risk.dao.RulerDao;
import com.zh.risk.dao.StrategyDao;
import com.zh.risk.entity.*;
import com.zh.risk.quartz.merchant.QlRunner;
import com.zh.risk.queue.DelayQueueManager;
import com.zh.risk.queue.DelayTask;
import com.zh.risk.service.CallRiskService;
import com.zh.risk.service.MerchInfoService;
import com.zh.risk.util.IPUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.aggregation.Aggregation;
import org.springframework.data.mongodb.core.aggregation.AggregationResults;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.stereotype.Service;

import java.util.*;

/**
 * code is far away from bug with the animal protecting
 * ┏┓　　　┏┓
 * ┏┛┻━━━┛┻┓
 * ┃　　　　　　　┃
 * ┃　　　━　　　┃
 * ┃　┳┛　┗┳　┃
 * ┃　　　　　　　┃
 * ┃　　　┻　　　┃
 * ┃　　　　　　　┃
 * ┗━┓　　　┏━┛
 * 　　┃　　　┃神兽保佑
 * 　　┃　　　┃代码无BUG！
 * 　　┃　　　┗━━━┓
 * 　　┃　　　　　　　┣┓
 * 　　┃　　　　　　　┏┛
 * 　　┗┓┓┏━┳┓┏┛
 * 　　　┃┫┫　┃┫┫
 * 　　　┗┻┛　┗┻┛
 *
 * @Description: 调用风控模型业务实现
 * @Author: Liard
 * @Create in: 2020/9/12 11:13
 * @Version: 1.0.1
 */
@Service
public class CallRiskServiceImpl implements CallRiskService {


    private static  final Logger log = LoggerFactory.getLogger(CallRiskServiceImpl.class);

    @Autowired
    private MongoTemplate mongoTemplate;

    @Autowired
    private StrategyDao dao;

    @Autowired
    private BlackListDao blackListDao;

    @Autowired
    private MerchInfoService merchInfoService;

    @Autowired
    private DelayQueueManager delayQueueManager;

    @Autowired
    private RulerDao rulerDao;

    @SpecialAop
    @Override
    public JSONResult<Object> checkTran(String param) throws MyServiceException {
        JSONResult<Object>  resMap = new JSONResult<>() ;
        Long  date  = new Date().getTime();
        JSONObject js = JSON.parseObject(param);
        if (Objects.isNull(js.get("strategyId"))) throw new MyServiceException("缺少必要参数,请输入策略编号strategyId",false);
        if (Objects.isNull(js.get("merchno"))) throw new MyServiceException("缺少必要参数,请输入商户编号merchno",false);
        if (Objects.isNull(js.get("amount"))) throw new MyServiceException("缺少必要参数,请输入交易金额amount",false);
        if (Objects.isNull(js.get("traceno"))) throw new MyServiceException("缺少必要参数,请输入订单编号traceno",false);
        if (Objects.isNull(js.get("ipAddress"))) throw new MyServiceException("缺少必要参数,请输入交易的ip地址",false);
        // 策略编号
        String id = js.get("strategyId").toString();
        // 平台商户号
        String plateMrcNo = js.get("merchno").toString();
        // 交易金额
        String amount = js.get("amount").toString();
        // 客户订单号
        String traceno = js.get("traceno").toString();
        //获取策略编号
        Strategy strategy = dao.selectById(id);
        // 交易的ip地址
        String ipAddress = js.get("ipAddress").toString();
        // 获取策略编号已绑定规则
        List<RiskMerchTrans> list = dao.getBindRulers(id);
        //命中得分
        int score=0;
        List<String> rulesList = new ArrayList<>();
        //判断商户的基础分值，如果超过阈值,直接拒绝交易
//        Map<String,String> MrcNoScore= merchInfoService.getMerchantListIsNo(plateMrcNo);
//        if(Objects.nonNull(MrcNoScore) && Integer.valueOf(MrcNoScore.get("score").toString()) >= Integer.valueOf(strategy.getRefuseScore())){
//            resMap.setData( new CheckResult("该商户的交易超过策略配置的阈值，终止交易",false));
//            return  resMap;
//        }
        if (CollUtil.isNotEmpty(list)) {
            for(int r=0 ;r<list.size();r++){
                RiskMerchTrans item = list.get(r);
                // 启用状态
                if (item.getStatus().equals(1)) {
                    //实时性
                    if (item.getTimeliness().equals(1)){
                        if(item.getGenre().equals(3)){//特殊处理
                            Map<String,Object> map = new HashMap<>();
                            map =  SpecialQLExpress(item,plateMrcNo,amount,ipAddress);
                            if(map.size()!=0){//表示已经触发特殊的规则
                                score +=(Integer) map.get("score");
                                List<String> rules = (List<String>) map.get("ruleList");
                                if(rules.size()!=0){
                                    rules.stream().forEach(val->{
                                        rulesList.add(val);
                                    });
                                }
                            }
                        }else{//模型规则处理
                            Map<String,Object> map = new HashMap<>();
                            map =  QLExpress(item,plateMrcNo,amount,ipAddress);//执行结果筛选
                            score +=(Integer) map.get("score");
                            List<String> rules = (List<String>) map.get("ruleList");
                            if(rules.size()!=0){
                                rules.stream().forEach(val->{
                                    rulesList.add(val);
                                });
                            }
                        }


                    } else {//延时性
                        //添加延时任务
                        item.setParam(param);
                        long valueTime = convertDateByMs(item.getUnit(),item.getNum());
                        DelayTask delayTask = new DelayTask(item , valueTime);
                        delayQueueManager.put(delayTask);
                    }
                }
            }
            //计算商户分值
            CheckResult checkResult =   calculation(score,plateMrcNo ,strategy);
            resMap.setData(checkResult);
//            if(!checkResult.getFlag()){
                if(rulesList.size()>0){
                    StringBuffer mesage = new StringBuffer();
                    mesage.append("此交易触发了以下风控条件：");
                    for (int i=0; i<rulesList.size();i++){
                        mesage.append(i+1+"、"+rulesList.get(i)+";");
                    }
                    resMap.setMessage(mesage.toString());
                }
//            }
        }else{
            resMap.setData( new CheckResult("交易成功",true));
        }
        Long resultDate =new Date().getTime();
        log.info ("风控总消耗时间：{}" ,(resultDate-date)+"ms") ;
        return resMap;
    }

    /**
     * 延时任务执行的方法
     * @param param
     * @return
     */
    @SpecialAop
    @Override
    public JSONResult<Object> checkTranDelay(RiskMerchTrans param){
        JSONResult<Object>  resMap = new JSONResult<>() ;
        JSONObject js = JSON.parseObject(param.getParam());
        // 策略编号
        String id = js.get("strategyId").toString();
        // 平台商户号
        String plateMrcNo = js.get("merchno").toString();
        // 交易金额
        String amount = js.get("amount").toString();
        // 客户订单号
        String traceno = js.get("traceno").toString();
        //获取策略编号
        Strategy strategy = dao.selectById(id);
        // 交易的ip地址
        String ipAddress = js.get("ipAddress").toString();
        //命中得分,规则执行结果和分数
        Map<String,Object> map = new HashMap<>();
        // 启用状态
        if (param.getStatus().equals(1)) {
            if(param.getGenre().equals(3)){//特殊处理
                map =  SpecialQLExpress(param,plateMrcNo,amount,ipAddress);
            }else{//模型规则处理
                map =  QLExpress(param,plateMrcNo,amount,ipAddress);
            }
        }
        //计算商户分值
        if(map.size()!=0){
            int score=(Integer) map.get("score");
            CheckResult checkResult =  calculation(score,plateMrcNo ,strategy);
            resMap.setData(checkResult);
        }
        return resMap;
    }

    @SpecialAop
    @Override
    public Boolean checkMrc(String param) throws MyServiceException {
        JSONObject js = JSON.parseObject(param);
        if (Objects.isNull(js.get("identityId"))) throw new MyServiceException("缺少必要参数,请输入法人身份证号identityId",false);
        String identityId = String.valueOf(js.get("identityId"));
        //List<RiskBlackList> lists = blackListDao.selectList(new LambdaQueryWrapper<RiskBlackList>().eq(RiskBlackList::getType, 2));
        List<RiskBlackList> lists = blackListDao.selectList(new LambdaQueryWrapper<RiskBlackList>().eq(RiskBlackList::getType, 2).eq(RiskBlackList::getRiskObj,identityId));
        if (lists.size() > 0) {
            return true;
        }
        return false;
    }

    /**
     * 模型规则处理：
     * 根据商户规则，获取对应的规则模型， mongodb聚合函数查询，利用QLExpress 计算返回命中规则的得分值
     * @param item 商户规则
     * @param plateMrcNo   商户编号
     * @param amount   交易金额
     * @param ipAddress   交易ip
     * @return
     */
    private Map<String,Object>  QLExpress(RiskMerchTrans item ,String plateMrcNo ,String amount ,String ipAddress ) {
        int score =0;
        List<String> rulesList = new ArrayList<>();
        Query query = new Query(Criteria.where("mrcId").is(String.valueOf(item.getId())));
        List<RiskModel> models = mongoTemplate.find(query, RiskModel.class);
        if(CollUtil.isNotEmpty(models)){
            for(int m =0;m<models.size();m++){
                RiskModel val = models.get(m);
                if(val.getAgg()==null){  //简易的条件
                    try {
                        ExpressRunner runner = QlRunner.getInstance().getRunner();
                        DefaultContext<String, Object> expressContext = new DefaultContext<String, Object>();
                        List<Item> itemList  =val.getItems();
                        StringBuffer testValue = new StringBuffer();
                        for(int i =0;i<itemList.size();i++){
                            Item v = itemList.get(i);
                            expressContext.put(v.getField()[1],Double.valueOf(amount));//设定字段的值
                            if("all".equals(v.getFilter())){
                                if(testValue.length()>0){
                                    testValue.append( " && " + v.getField()[1] + v.getOperator()[1] +v.getFilterStr()+" ");//程序文本
                                }else{
                                    testValue.append(v.getField()[1] + v.getOperator()[1] +v.getFilterStr()+" ");//程序文本
                                }

                            } else if("any".equals(v.getFilter())){
                                if(testValue.length()>0){
                                    testValue.append(" || " + v.getField()[1] + v.getOperator()[1] +v.getFilterStr());//程序文本
                                }else{
                                    testValue.append(v.getField()[1] + v.getOperator()[1] +v.getFilterStr()+" ");//程序文本
                                }
                            }
                        }
                        log.info("执行的风控规则模型名称：{}",val.getName());
                        Boolean d = (Boolean) runner.execute(testValue.toString(), expressContext,null, false, false);
                        if(d){
                            score=score+val.getScore();
                            rulesList.add(val.getName());
                            //log.error("触发条件：{},结果：{}",val.getName(),d);
                        }
                    }catch (Exception e){
                         e.printStackTrace();
                    }

                }else{
                    String[] dateVar = convertDate(val.getUnit(), val.getNum());
                    //商户编号
                    Criteria criteria = Criteria.where("merchno").is(plateMrcNo);
                    Criteria criteria1 = new Criteria();//存放年月日类型 2020-10-13
                    Criteria criteria2 = new Criteria();////存放年月日类型 2020-10-13
                    Criteria criteria3 = new Criteria();//存放小时类型 列：01:01:10
                    Criteria criteria4 = new Criteria();//存放小时类型 列：01:01:10
                    Criteria criteria5 = new Criteria();//附加条件
                    if(!Objects.isNull(val.getParam())  &&  !Objects.isNull(val.getParam().get(0).getField()) && !Objects.isNull(val.getParam().get(0).getFilterStr()) && !Objects.isNull(val.getParam().get(0).getOperator()) ){
                        String paramFieldName = val.getParam().get(0).getField()[1];//字段名称
                        //条件值,如果输入值为now，则获取当前参数交易ip地址，否则获取条件值，
                        String paramValue =  val.getParam().get(0).getFilterStr();
                        if(Objects.equals("ipAddress",val.getParam().get(0).getFilterStr())){
                            paramValue =  ipAddress;
                         }
                        String paramOperator = val.getParam().get(0).getOperator()[1];//操作符
                        criteria5 = statisticOperator(paramFieldName,paramValue,paramOperator);
                    }
                    if("hour".equals(val.getUnit()) || "minutes".equals(val.getUnit()) || "seconds".equals(val.getUnit())){
                        criteria1 = criteria1.where("trans_date").is(dateVar[0].split(" ")[0]);
                        criteria3 = criteria3.where("trans_time").lt(dateVar[0].split(" ")[1]);//小于
                        criteria4 =  criteria4.where("trans_time").gte(dateVar[1].split(" ")[1]);//大于等于
                    }else{
                        criteria1 = criteria1.where("trans_date").lte(dateVar[0].split(" ")[0]);//小于等于，用于查询当天的数据，因为qrcode_order保留的是当天的数据
                        criteria2 =  criteria2.where("trans_date").gte(dateVar[1].split(" ")[0]);//大于等于 //存放年月日类型 2020-10-13
                    }
                    // 需要查询的表名称
                    String searchName = val.getSeaField()[0];
                    // 需要查询的字段
                    String searchField = val.getSeaField()[1];
                    //查询搜索的结果,暂无

                    // 需要统计表名称
                    String staName = val.getStaField()[0];
                    // 需要统计表字段
                    String staField = val.getStaField()[1];
                    String findAgg = val.getAgg();
                    Aggregation agg = null;
                    //聚合函数
                    if (findAgg.equals("countVal")) {
                        agg = Aggregation.newAggregation(
                                Aggregation.match(criteria),
                                Aggregation.match(criteria1),
                                Aggregation.match(criteria2),
                                Aggregation.match(criteria3),
                                Aggregation.match(criteria4),
                                Aggregation.match(criteria5),
                                Aggregation.count().as(findAgg)
                        );
                    } else if (findAgg.equals("sumVal")) {
                        agg = Aggregation.newAggregation(
                                Aggregation.match(criteria),
                                Aggregation.match(criteria1),
                                Aggregation.match(criteria2),
                                Aggregation.match(criteria3),
                                Aggregation.match(criteria4),
                                Aggregation.match(criteria5),
                                Aggregation.group(plateMrcNo).sum(staField).as(findAgg)
                        );
                    } else if (findAgg.equals("avgVal")) {
                        agg = Aggregation.newAggregation(
                                Aggregation.match(criteria),
                                Aggregation.match(criteria1),
                                Aggregation.match(criteria2),
                                Aggregation.match(criteria3),
                                Aggregation.match(criteria4),
                                Aggregation.match(criteria5),
                                Aggregation.group(plateMrcNo).avg(staField).as(findAgg)
                        );
                    } else if (findAgg.equals("maxVal")) {
                        agg = Aggregation.newAggregation(
                                Aggregation.match(criteria),
                                Aggregation.match(criteria1),
                                Aggregation.match(criteria2),
                                Aggregation.match(criteria3),
                                Aggregation.match(criteria4),
                                Aggregation.match(criteria5),
                                Aggregation.group(plateMrcNo).max(staField).as(findAgg)
                        );
                    } else if (findAgg.equals("minVal")) {
                        agg = Aggregation.newAggregation(
                                Aggregation.match(criteria),
                                Aggregation.match(criteria1),
                                Aggregation.match(criteria2),
                                Aggregation.match(criteria3),
                                Aggregation.match(criteria4),
                                Aggregation.match(criteria5),
                                Aggregation.group(plateMrcNo).min(staField).as(findAgg)
                        );
                    }
                    if (Objects.isNull(agg)) break;
                    AggregationResults<DbResult> results = mongoTemplate.aggregate(agg, staName, DbResult.class);
                    String num = statisticVal(findAgg, results);
                    //System.err.println("统计结果："+num);
                    try {
                        ExpressRunner runner = QlRunner.getInstance().getRunner();
                        DefaultContext<String, Object> expressContext = new DefaultContext<String, Object>();
                        List<Item> itemList  =val.getItems();
                        StringBuffer testValue = new StringBuffer();
                        for (int i =0;i<itemList.size();i++){
                            Item v = itemList.get(i);
                            if(findAgg.equals("avgVal") ){
                                expressContext.put(v.getField()[1],Double.valueOf(num));//设定字段的值
                            } else {
                                expressContext.put(v.getField()[1],Integer.valueOf(num));//设定字段的值
                            }
                            if("all".equals(v.getFilter())){
                                if(testValue.length()>0){
                                    testValue.append( " && " + v.getField()[1] + v.getOperator()[1] +v.getFilterStr()+" ");//程序文本
                                }else{
                                    testValue.append(v.getField()[1] + v.getOperator()[1] +v.getFilterStr()+" ");//程序文本
                                }

                            } else if("any".equals(v.getFilter())){
                                if(testValue.length()>0){
                                    testValue.append(" || " + v.getField()[1] + v.getOperator()[1] +v.getFilterStr());//程序文本
                                }else{
                                    testValue.append(v.getField()[1] + v.getOperator()[1] +v.getFilterStr()+" ");//程序文本
                                }
                            }
                        }
                        //System.err.println("规则名称："+val.getName());
                        // 执行表达式
                        //System.err.println("程序文本输出："+testValue);
                        //符合条件则返回true,不符合条件返回false，即可以交易
                        log.info("执行的风控规则模型名称：{}",val.getName());
                        Boolean d = (Boolean) runner.execute(testValue.toString(), expressContext,null, false, false);
                        // System.err.println("执行表达式结果:"+d);
                        if(d){
                            score=score+val.getScore();
                            rulesList.add(val.getName());
                            //System.err.println("符合条件，禁止交易");
                                       /* resMap.put("success", true);
                                        resMap.put("msg", "此交易因符合 '"+ val.getName() +"' 条件，终止交易");
                                        return resMap;*/
                        }/*else{
                            System.err.println("不符合条件，可以交易,继续执行其他条件");
                        }*/
                    }catch (Exception e){
                        e.printStackTrace();
                    }
                }
            }
        }/*else{
            System.err.println("Mongodb中没有对应的模型规则");
        }*/
        Map<String,Object> map = new HashMap<>();
        map.put("score",score);
        map.put("ruleList",rulesList);
        return  map;
    }

    /**
     * 特殊处理：
     * 同一个用户1分钟内不允许同金额二次成功支付
     * 修改结算账户频繁（5天修改3次，10天修改5次）
     * 相同交易金额连续笔数限制
     * 针对商户类型做交易金额限制
     * @param item  商户规则
     * @param plateMrcNo  商家编号
     * @param amount   交易金额
     * @return
     */
    private Map<String,Object> SpecialQLExpress(RiskMerchTrans item ,String plateMrcNo , String amount,String ipAddress){
        Long  date  = new Date().getTime();
        String[] paramValue =  item.getDescription().trim().split(";");
        Map<String,Object> map = new HashMap<>();
        List<String> rulesList = new ArrayList<>();
        if("3".equals(paramValue[0])){//5天修改3次
               String sql = paramValue[1].replace("商户号",plateMrcNo);
               RiskMySQL riskMySQL = new RiskMySQL();
               riskMySQL.setSql(sql);
               int count = rulerDao.getMerchUpdateName(riskMySQL);
               if(count>=3){
                   rulesList.add(item.getName());
                   map.put("score",item.getScore());
                   map.put("ruleList",rulesList);
               }
            Long resultDate =new Date().getTime();
            log.info ("{}，消耗时间：{}",item.getName() ,(resultDate-date)+"ms") ;
        }else if("5".equals(paramValue[0])){//10天修改5次
            String sql = paramValue[1].replace("商户号",plateMrcNo);
            RiskMySQL riskMySQL = new RiskMySQL();
            riskMySQL.setSql(sql);
            int count = rulerDao.getMerchUpdateName(riskMySQL);
            if(count>=5){
                rulesList.add(item.getName());
                map.put("score",item.getScore());
                map.put("ruleList",rulesList);
            }
            Long resultDate =new Date().getTime();
            log.info ("{}，消耗时间：{}" ,item.getName(),(resultDate-date)+"ms") ;
        }else if("相同交易金额连续笔数限制".equals(paramValue[0])){//相同交易金额连续笔数限制
            int orderNum = dao.getQrcodeOrderNum(plateMrcNo);//获取今天交易的总数 str.substring(str.length()-1,str.length());
            int count = Integer.valueOf(paramValue[1].substring(paramValue[1].length()-1,paramValue[1].length()));
            //获取最后参数的值，判断交易总数是否足够.
            if(orderNum>=count){
                String sql = paramValue[1].replace("商户号",plateMrcNo);
                RiskMySQL riskMySQL = new RiskMySQL();
                riskMySQL.setSql(sql);
                //判断前多少笔数据的交易金额是否与当前相同
                List<Double> resMap = dao.getQrcodeOrderAmount(riskMySQL);
                boolean hasSameRes = hasSame(resMap);
                Double money =Double.valueOf(amount);
                if(hasSameRes && resMap.get(0).equals(money)){//前几笔都相同，并当前这笔都与前几笔的金额相同，则拒绝
                    rulesList.add(item.getName());
                    map.put("score",item.getScore());
                    map.put("ruleList",rulesList);
                }
            }
            Long resultDate =new Date().getTime();
            log.info ("{}，不可以超过{}笔相同，消耗时间：{}",item.getName() ,count,(resultDate-date)+"ms") ;
        }else if("merch_type".equals(paramValue[0])){//针对商户类型做交易金额限制
            String sql = paramValue[1].replace("?",plateMrcNo);
            RiskMySQL riskMySQL = new RiskMySQL();
            riskMySQL.setSql(sql);
            int  merchType= dao.getMerchTypeBySQL(riskMySQL);
            if(merchType!=0 && Double.valueOf(amount)>=500){//小微商户单笔限额为500
               rulesList.add(item.getName());
               map.put("score",item.getScore());
               map.put("ruleList",rulesList);
            }
            Long resultDate =new Date().getTime();
            log.info ("{}，消耗时间：{}" ,item.getName(),(resultDate-date)+"ms") ;
        }else if("minute".equals(paramValue[0])) {//同一个用户1分钟内不允许同金额二次成功支付
            //查询该用户一分钟内容交易的数据，可能有多条，判断金额是否与当前交易的金额相同
            int  res = dao.repeatOrders(plateMrcNo,amount);
            if(res>0){
                rulesList.add(item.getName());
                map.put("score",item.getScore());
                map.put("ruleList",rulesList);
            }
            Long resultDate =new Date().getTime();
            log.info ("{}，消耗时间：{}" ,item.getName(),(resultDate-date)+"ms") ;
        } else if("time".equals(paramValue[0])) {//交易时间：晚上11点-早上6点之间，大金额连续交易限制，判断当前交易是否在23点到23:59:59和在0点到6点
            Date dateNow =DateUtil.dateSecond();//当前时间
            Date dateStart = DateUtil.beginOfDay(new Date());//今天的开始时间
            Date dateEnd =  DateUtil.endOfDay(new Date());//今天的结束时间
            Date date23 = DateUtil.parseDateTime(DateUtil.today()+" 23:00:00");//获取今天23点的时间
            Date date06 = DateUtil.parseDateTime(DateUtil.today()+" 06:00:00");//获取今天06点的时间
            boolean res = DateUtil.isIn(dateNow,date23,dateEnd);//判断当前时间是否在23点到23:59:59这个范围
            boolean result = DateUtil.isIn(dateNow,dateStart,date06);//判断当前时间是否在0点到6点这个范围
            if(res || result){//如果在这两个其中一个范围，则执行
                int num = res ? 1 : 0; //1表示23点到23:59:59这个范围，不需要查历史数据,直接查qrcode_order表 ; 0表示0点到6点这个范围，需要查前一天23点之后的历史数据qrcode_order_history和qrcode_order
                map = SpecialQLExpressTimes(item,plateMrcNo,amount,num);
            }
            Long resultDate =new Date().getTime();
            log.info ("{}，消耗时间：{}",item.getName() ,(resultDate-date)+"ms") ;
        }else if("当天累计交易金额".equals(paramValue[0])){ //单个商户编号的累计交易金额
            Long resultDate =new Date().getTime();
            map = SpecialQLExpressMoney(item,plateMrcNo,amount);
            log.info ("{}，消耗时间：{}",item.getName() ,(resultDate-date)+"ms") ;
        }else if("交易波动频繁".equals(paramValue[0])){//交易波动频繁
            Long resultDate =new Date().getTime();
            map = SpecialQLExpressOften(item,plateMrcNo,amount);
            log.info ("{}，消耗时间：{}" ,item.getName(),(resultDate-date)+"ms") ;
        }else if("ip".equals(paramValue[0])){//短时间内异地登录（例： 广州登录1小时后北京登录）
            Long resultDate =new Date().getTime();
            map = SpecialQLExpressOtherLogin(item,plateMrcNo,amount,ipAddress);
            log.info ("{}，消耗时间：{}" ,item.getName(),(resultDate-date)+"ms") ;
        }
        return  map;
    }


    /**
     * 交易时间：晚上11点-早上6点之间，大金额连续交易限制
     * @param item 商户规则
     * @param plateMrcNo  商家编号
     * @param amount 交易金额
     * @param num
     * @return
     */
    private Map<String,Object> SpecialQLExpressTimes(RiskMerchTrans item ,String plateMrcNo , String amount, int num){
        String[] paramValue =  item.getDescription().trim().split(";");
        double money = Double.valueOf(paramValue[1]);//限制的金额
        int count  = Integer.valueOf(paramValue[2]);//限制的连续交易笔数
        Map<String,Object> map = new HashMap<>();
        List<String> rulesList = new ArrayList<>();
        List<Double> result = new ArrayList<>();
        if(Objects.equals(1,num)){ //  1表示23点到23:59:59这个范围，不需要查历史数据, 直接查询qrcode_order当天23点之后的数据
            result = dao.getToDayLast(plateMrcNo);
        }else {// 0 表示0点到6点这个范围，需要查前一天23点之后的历史数据,查询qrcode_order_history和qrcode_order表
            result = dao.getToDaySix(plateMrcNo);
            List<Double> resultHistory = dao.getToDayLastByHistory(plateMrcNo);
            for(int i=0 ;i<resultHistory.size();i++){
                result.add(resultHistory.get(i));
            }
        }
        int k =0;
        //大金额连续交易判断,交易笔数足够限制的笔数
        if(result.size()!=0 && result.size()>=count){
            for (int i=0; i<count;i++){
                 double lastMony = result.get(i);
                 if(lastMony>=money){
                      k=k+1;
                 }
            }
        }
        if(k>=count && Double.valueOf(amount)>=money){//前几笔都大于或者等于限制的金额，并当前交易的金额也大于或者等于限制的金额 ，则禁止交易
            rulesList.add(item.getName());
            map.put("score",item.getScore());
            map.put("ruleList",rulesList);
        }
        return map;
    }

    /**
     * 单个商户编号的累计交易金额：正常情况下，单个商户编号一天的累计交易金额逐步增加至50万-100万之间（如果有一天忽然有500万即可定为风险)
     * @param item 商户规则
     * @param plateMrcNo  商家编号
     * @param amount 交易金额
     * @return
     */
    private Map<String,Object> SpecialQLExpressMoney(RiskMerchTrans item ,String plateMrcNo , String amount){
        //String[] paramValue =  item.getDescription().trim().split(";");
        Map<String,Object> map = new HashMap<>();
        List<String> rulesList = new ArrayList<>();
        List<Double> historyData = dao.getEveryDayAmount(plateMrcNo);//获取历史每天交易的金额总数
        if(historyData.size()>0){
            List<Double> data = dao.getToDayAmount(plateMrcNo);//获取今天交易的总金额
            if(data.size()!=0){
                //获取商户交易最少的交易金额
                //double minValue = historyData.stream().mapToDouble(a->a).min().getAsDouble();
                //获取商户某天交易最高的交易金额
                double MaxValue =  historyData.stream().mapToDouble(a->a).max().getAsDouble();
                double param = MaxValue * 0.35 + MaxValue; //最高的值; 计算方式：历史平均每天最高值 * 35% + 历史平均每天最高值 = 最高的值
                double toDayAmount  = data.get(0);
                if(toDayAmount >= param){ //当前交易金额超过历史最高的35%,则有问题
                    rulesList.add(item.getName());
                    map.put("score",item.getScore());
                    map.put("ruleList",rulesList);
                }
            }
        }
        return map;
    }

    /**
     * 短时间内异地登录（例： 广州登录1小时后北京登录）;
     * 1、判断当前交易的ip地址，是否在上一笔的交易ip地址这个区域内
     * @param item 商户规则
     * @param plateMrcNo  商家编号
     * @param amount 交易金额
     * @param ipAddress 交易的ip
     * @return
     */
    private Map<String,Object> SpecialQLExpressOtherLogin(RiskMerchTrans item ,String plateMrcNo , String amount ,String ipAddress){
        Map<String,Object> map = new HashMap<>();
        List<String> rulesList = new ArrayList<>();
        Map<String,Object> result = dao.getLastIP(plateMrcNo);//获取上一笔的交易iP
        String  lastIP = result.get("cust2").toString();
        String  lastTime = result.get("transTime").toString();
        if(Objects.nonNull(lastIP) && Objects.nonNull(lastTime)){
            String lastAddress = IPUtil.getCityInfo(lastIP); //解析上一笔的交易ip地址
            String nowAddress =  IPUtil.getCityInfo(ipAddress);//解析当前交易ip地址
            if(Objects.nonNull(lastAddress) && Objects.nonNull(nowAddress) &&   !Objects.equals(lastAddress,nowAddress)){ //两个交易的地址不同
                DateTime date = DateUtil.parse(lastTime,"yyyy-MM-dd HH:mm:ss");
                String  value =  DateUtil.formatBetween(date,DateUtil.date());
                int i =0;
                if(value.contains("时") || value.contains("天")){//含有这两个的则已经超过一个小时
                     i=1;
                }
                if(Objects.equals(0,i)){ //如果两笔交易时间 ， 间隔 不超过1小时，即就是一个小时内，两次的交易地址不同
                        rulesList.add(item.getName());
                        map.put("score",item.getScore());
                        map.put("ruleList",rulesList);
                        log.info("上一笔交易地址：{}，当前交易地址：{}",lastAddress,nowAddress);
                }
            }
        }
        return map;
    }

    /**
     * 交易波动频繁  统计当天+前两天的平均值，上下浮动超过25%的就算有问题
     * @param item 商户规则
     * @param plateMrcNo  商家编号
     * @param amount 交易金额
     * @return
     */
    private Map<String,Object> SpecialQLExpressOften(RiskMerchTrans item ,String plateMrcNo , String amount){
        Map<String,Object> map = new HashMap<>();
        List<String> rulesList = new ArrayList<>();
        List<Double> lastThereAmount = dao.getLastThereDayAmount(plateMrcNo);//获取前三天的交易金额
        List<Double> lastTwoDayAmount = dao.getLastTwoDayAmount(plateMrcNo);//获取前两天的交易金额
        if(lastThereAmount.size()>0){
            double thereAvg  = lastThereAmount.stream().mapToDouble(a->a).sum() / 3;//获取前三天的平均交易总金额
            //获取商户类型
            int type =dao.getMerchType(plateMrcNo);
            int avgMoney = 1500;
            if(Objects.equals(type,2)){//企业类型
                avgMoney=5000;
            }
            List<Double> toDayAmount = dao.getToDayAmount(plateMrcNo);//获取今天交易的总金额
            if(toDayAmount.size()>0 && thereAvg>avgMoney){//前三天的平均数要大于设定的值
                //获取当天和前两天的平均交易总金额
                toDayAmount.addAll(lastTwoDayAmount);
                double twoAvg  = toDayAmount.stream().mapToDouble(a->a).sum() / 3;//获取当天和前两天的平均交易总金额
                if(twoAvg>avgMoney){//后三天的平均数要大于设定的值
                    double  maxilla = thereAvg * 0.25 + thereAvg ;//最高的上浮值; 计算方式：前三天平均交易总金额 * 25% + 原来的前三天平均交易总金额 = 上浮值
                    double  minilla = thereAvg * 0.75;//最低的下浮值 ; 计算方式：前三天平均交易总金额 * 75% = 下浮值
                    if(twoAvg>maxilla || twoAvg<minilla ){//大于上浮值，有问题 ； //低于下浮值，有问题
                        rulesList.add(item.getName());
                        map.put("score",item.getScore());
                        map.put("ruleList",rulesList);
                    }
                    //log.info("该商户最高可超上浮值为：{}，该商户最低下浮值为",maxilla,minilla);
                }

            }
        }
        return map;
    }



    /**
     * 根据不同类型聚合值获取对应的统计值
     * @param val
     * @param results
     */
    private String statisticVal(String val, AggregationResults<DbResult> results) {
        String num = "";
        switch (val) {
            case "countVal":
                num = Objects.equals(results.getMappedResults().size(),0) ? "0" : results.getMappedResults().get(0).getCountVal().toString();
                break;
            case "avgVal":
                num = Objects.equals(results.getMappedResults().size(),0)  ? "0" : results.getMappedResults().get(0).getAvgVal().toString();
                break;
            case "sumVal":
                num = Objects.equals(results.getMappedResults().size(),0)  ? "0" : results.getMappedResults().get(0).getSumVal().toString();
                break;
            case "maxVal":
                num = Objects.equals(results.getMappedResults().size(),0)  ? "0" : results.getMappedResults().get(0).getMaxVal().toString();
                break;
            case "minVal":
                num = Objects.equals(results.getMappedResults().size(),0)  ? "0" : results.getMappedResults().get(0).getMinVal().toString();
                break;
        }
        return num;
    }



    /**
     * 转换时间
     * @param dateChar 传入时间片类型
     * @param num      数值
     * @return
     */
    private String[] convertDate(String dateChar, Integer num) {
        Date date = DateUtil.date();
        String[] d = new String[]{DateUtil.formatDateTime(date), ""};
        switch (dateChar) {
            case "year": {
                d[1] = DateUtil.formatDateTime(DateUtil.offset(date, DateField.YEAR, -num));
                break;
            }
            case "month":
                d[1] = DateUtil.formatDateTime(DateUtil.offset(date, DateField.MONTH, -num));
                break;
            case "day": {
                d[1] = DateUtil.formatDateTime(DateUtil.offset(date, DateField.DAY_OF_MONTH, -num));
                break;
            }
            case "hour": {
                d[1] = DateUtil.formatDateTime(DateUtil.offset(date, DateField.HOUR, -num));
                break;
            }
            case "minutes": {
                d[1] = DateUtil.formatDateTime(DateUtil.offset(date, DateField.MINUTE, -num));
                break;
            }
            case "seconds": {
                d[1] = DateUtil.formatDateTime(DateUtil.offset(date, DateField.SECOND, -num));
                break;
            }

        }
        return d;
    }

    /**
     * 根据操作符返回不同的语句
     * @param paramFieldName   字段名称
     * @param paramValue    条件值
     * @param paramOperator  操作符
     * @return
     */
    private Criteria statisticOperator(String paramFieldName,String paramValue,String paramOperator) {
        Criteria result =new Criteria();
        switch (paramOperator) {
            case "==":
                result = result.where(paramFieldName).is(paramValue);
                break;
            case ">":
                result = result.where(paramFieldName).gt(paramValue);
                break;
            case "<":
                result = result.where(paramFieldName).lt(paramValue);
                break;
            case ">=":
                result = result.where(paramFieldName).gte(paramValue);
                break;
            case "<=":
                result = result.where(paramFieldName).lte(paramValue);
                break;
            case "like":
                result = result.where(paramFieldName).regex(paramValue);
                break;
        }
        return result;
    }

    /**
     * 计算商户分值，并返回交易结果
     * @param score  规则总得分
     * @param plateMrcNo  商户编号
     * @param strategy   策略
     * @return
     */
    private CheckResult calculation(int score,String plateMrcNo ,Strategy strategy) {
        CheckResult checkResult =null;
        //根据商户编号，获取商户的基础分值
        Map<String,String> MrcNoScore= merchInfoService.getMerchantListIsNo(plateMrcNo);
        //修改商户的得分，然后判断
        int resultSroce  = Objects.isNull(MrcNoScore)  ? 0+score :  Integer.valueOf(MrcNoScore.get("score"))+score;
        //如果没有得分，则不用修改
        if(!Objects.equals(score,0)){
            int  resultInt= merchInfoService.updateMerchInfoScore(plateMrcNo,String.valueOf(resultSroce));
        }
        if(resultSroce >= Integer.valueOf(strategy.getWarnScore())){
            if(resultSroce >= Integer.valueOf(strategy.getRefuseScore())){
                checkResult = new CheckResult("该交易超过策略配置的阈值，终止交易",false);
            }else{
                checkResult = new CheckResult("交易成功,但该交易有风险",true);
            }
        }else{
            checkResult = new CheckResult("交易成功",true);

        }
        return  checkResult;
    }

    /**
     * 时分秒 转换 毫秒，用于延时任务
     * @param dateChar 传入时间片类型
     * @param num      数值
     * @return
     */
    private long convertDateByMs(String dateChar, Integer num) {
        long d =new Long(0);
        switch (dateChar) {
            case "hour": {
                d = 60*60*1000*num;
                break;
            }
            case "minutes": {
                d = 60*1000*num;
                break;
            }
            case "seconds": {
                d = 1000*num;
                break;
            }
        }
        return d;
    }

    /**
     * 判断List中的元素是否完全相同
     * @param list
     * @return
     */
    private  boolean hasSame(List<? extends Object> list)
    {
        if(null == list)
            return false;
        return 1 == new HashSet<Object>(list).size();
    }
}
